-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement inline override #8543
Implement inline override #8543
Conversation
de074d9
to
58e4738
Compare
Calls to unapply are generated by the typer, so when would we need them to be kept at runtime? In fact, #8542 adds support for |
Note that this could also allow us to support the implementation of an abstract method with an inline method trait Hello:
def hello: String
object App extends Hello:
inline def hello: String = "hi" |
I am not sure whether we need retained inline unapplys and whether they have to be user-level. @nicolasstucki do you have a typical usecase? When trying to implement this PR, I noted a potential problem. It's in inline override def alternative(inline n: Int) <: GenericProduct[_ <: Lst[T]] =
inline n match {
case 0 => Cons.GenericCons[T]
case 1 => Nil.GenericNil If we translate that with the described scheme, we get:
The problem is that the retained version of I see two feasible ways around this
inline erased override def alternative(inline n: Int) <: GenericProduct[_ <: Lst[T]] = ... An Opinions? |
Drop unused AvoidClashName name kind and name tags AVIDCLASH and DIRECT
58e4738
to
b84078d
Compare
@odersky The problematic example has a very specific property: the overridden method is never actually correct to begin with. It has Moreover, the example clearly shows that it can only work if that method is overridden in the "virtual" dispatch when it is called. It's just that this always happens statically. Again, this highlights that the true overriding semantics is what this example relies on. What we need is therefore not a way to bypass overriding semantics with an I would therefore like to declare |
I'm not a fan of either way, seems like too much complexity for too little gains. I think it's OK to have stricter requirements for the bodies of |
I am not sure if the The code for inline override def alternative(inline n: Int) <: GenericProduct[_ <: Lst[T]] =
n match { // remove the inline here
case 0 => Cons.GenericCons[T]
case 1 => Nil.GenericNil This means that if the code is not inlined, the match will be retained. If the code is inlined, it will reduce the match. Alternatively, we could say the Another alternative is to write the code as follows inline override def alternative(inline n: Int) <: GenericProduct[_ <: Lst[T]] =
inline n match
case 0 => Cons.GenericCons[T]
case 1 => Nil.GenericNil
case _ =>
// non-specialized implementation (not necessarily the same as the inlined code)
n match
case 0 => Cons.GenericCons[T]
case 1 => Nil.GenericNil |
@sjrd Good analysis. I think abstract inline could be the way to go here. I'll try that next. |
0977cc6
to
a8bdfe5
Compare
Will this have any effect over the ability to combine |
@soronpo No it won't, neither positively nor negatively. |
f238e74
to
4d0fc00
Compare
``` | ||
The inlined invocations and the dynamically dispatched invocations give the same results. | ||
|
||
2. Inline methods can override or implement normal methods, as the previous example shows. Inline methods can be overridden only by other inline methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum, I don't think we should allow concrete inline methods to be overridden, even by other inline methods. This can break the semantics of polymorphic dispatch, because we might not flag a call to a method of the superclass on an instance that, at runtime, will be of a subclass.
It works with abstract inline methods because attempts at calling those will be flagged if they cannot be resolved to an overriding concrete inline method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried with the last commit but it breaks scalatest. We have two inline versions of assert
; one in Assertions the other in Diagrams. What scalatest does does not look unreasonable. It's a pity that this means we get static behavior which is not backed by runtime behavior. But it does not look easy to find workarounds in existing software.
So I think we will have to revert this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(for reference the definitions in scalatest are https://github.com/scalatest/scalatest/blob/4e1e38e2372ddada9037d442aa7efed1418119d9/scalatest.dotty/src/main/scala/org/scalatest/Assertions.scala#L470 and https://github.com/scalatest/scalatest/blob/4e1e38e2372ddada9037d442aa7efed1418119d9/scalatest.dotty/src/main/scala/org/scalatest/diagrams/Diagrams.scala#L180).
I'm still strongly in favor of disallowing this. As @sjrd said, it just has the wrong semantics. Scalatest will have to be slightly refactored to avoid the override: put the code for the regular assert and diagram assert in the same macro, and pass a boolean to the macro to decide which branch to use, this boolean could come from a def in the trait that can be overridden in Diagrams to preserve the current API. This does not allow users to overrides assert themselves like the previous version could, but we can't do that without breaking semantics so that's not a bad thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure. What you propose is a breach of modularity. Assertions now has to know that there is an override in Diagrams. This might work in this concrete example, but I am not sure where else this pattern is used.
So, in the abstract it's better to disallow but in the concrete I personally do not want to deal with the breakage this causes. I'll revert for now and open an issue. If someone wants to take this up, fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you propose is a breach of modularity.
inline defs inherently break modularity, better make that obvious than have surprising semantics IMO. But we can indeed discuss this separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it, an abstract inline def assert in a base trait would probably suite scalacheck well, but it might require a bigger refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For scalatest the refactoring seems simple. We can have a single macro in Assertions
that takes this
as an argument. If this
is statically known to be Diagrams
we call DiagrammedAssertionsMacro.assert
otherwise we call AssertionsMacro.assert
. I will try it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it was simple #8601
dad18a2
to
793eaec
Compare
The previous scheme was more complicated and looks more fragile, in particular if complex staging operations are applied to a retained inline method. In that case we have to rely on the fact that the rhs of a retained inline method is in fact its inlineable body, and not the retained body.
793eaec
to
28c49f7
Compare
f110b3b
to
051039e
Compare
This reverts commit 051039e.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of the retained
methods logic LGTM.
We still need to decide on #8564. I strongly suggest making them final.
Also not sure about the usefulness of the abstract inline
.
All these concerns can be handled in followup PRs.
@@ -279,6 +279,9 @@ object TastyFormat { | |||
|
|||
final val INLINEACCESSOR = 21 // The name of an inline accessor `inline$name` | |||
|
|||
final val BODYRETAINER = 22 // The name of a synthetic method that retains the runtime | |||
// body of an inline method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing from the documentation of Name
above (line 40)
The idea is to support
inline override
methods that can be both inlined and called dynamically. The old restriction that an inline method can only override concrete methods will go away. The implementation idea is as follows:A definition
is desugared to two methods:
At erasure, assume the two methods look like this:
They are then rewritten to
Here, the owner of
body2
is changed tof
and all references to parameters off$retainedBody
arechanged to references of corresponding parameters in
f
.An open question is whether we should go further and apply this scheme also if there is no
override
present. This could be useful e.g. for being able to inlineunapply
methods, sinceunapply
methods have to be kept around at runtime as well. A possible syntax for this would be a soft modifierretained
(the opposite oferased
). I.eThis would give an inline unapply that persists after erasure, just like the
f
above.